#!/usr/bin/python
# -*- coding: utf-8 -*-  
#
# Copyright (C) 2012 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

__author__ = 'afshar@google.com (Ali Afshar)'


# Add the library location to the path
import sys
sys.path.insert(0, 'lib')

import os
import httplib2
import sessions
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
from google.appengine.ext.webapp import template
from apiclient.discovery import build
from apiclient.http import MediaUpload
from oauth2client.client import flow_from_clientsecrets
from oauth2client.client import FlowExchangeError
from oauth2client.client import AccessTokenRefreshError
from oauth2client.appengine import CredentialsProperty
from oauth2client.appengine import StorageByKeyName
from oauth2client.appengine import simplejson as json


ALL_SCOPES = ('https://www.googleapis.com/auth/drive.file '
              'https://www.googleapis.com/auth/userinfo.email '
              'https://www.googleapis.com/auth/userinfo.profile')


# MYNOTESFOLDER = "MyNotes"
# MyNotes_folderid = "0B7W9AXhTJJq6dnNxRnlMREN0WEU"
MyNotes_folderid = None                   # this value got from google database # get through a function

dummyNode = [None, None, None, None, None]
deletedNode = ["deleted", None, None, None, None]
# errorNode = ["error", None, None, None, None]


def getMyNotesFolderId():
  # get the notes folder id
  entity = MyDatabase.get_or_insert("MyNotes_folderid")
  if entity.content:
    return entity.content
  else:
    return None



def SibPath(name):
  """Generate a path that is a sibling of this file.

  Args:
    name: Name of sibling file.
  Returns:
    Path to sibling file.
  """
  return os.path.join(os.path.dirname(__file__), name)


# Load the secret that is used for client side sessions
# Create one of these for yourself with, for example:
# python -c "import os; print os.urandom(64)" > session-secret
SESSION_SECRET = open(SibPath('session.secret')).read()
INDEX_HTML = open(SibPath('index.html')).read()



class Credentials(db.Model):
  """Datastore entity for storing OAuth2.0 credentials.

  The CredentialsProperty is provided by the Google API Python Client, and is
  used by the Storage classes to store OAuth 2.0 credentials in the data store."""
  credentials = CredentialsProperty()


def CreateService(service, version, creds):
  """Create a Google API service.

  Load an API service from a discovery document and authorize it with the
  provided credentials.

  Args:
    service: Service name (e.g 'drive', 'oauth2').
    version: Service version (e.g 'v1').
    creds: Credentials used to authorize service.
  Returns:
    Authorized Google API service.
  """
  # Instantiate an Http instance
  http = httplib2.Http()

  # Authorize the Http instance with the passed credentials
  creds.authorize(http)

  # Build a service from the passed discovery document path
  return build(service, version, http=http)


class DriveState(object):
  """Store state provided by Drive."""

  def __init__(self, state):
    """Create a new instance of drive state.

    Parse and load the JSON state parameter.

    Args:
      state: State query parameter as a string.
    """
    if state:
      state_data = json.loads(state)
      self.action = state_data['action']
      self.ids = map(str, state_data.get('ids', []))
    else:
      self.action = 'create'
      self.ids = []

  @classmethod
  def FromRequest(cls, request):
    """Create a Drive State instance from an HTTP request.

    Args:
      cls: Type this class method is called against.
      request: HTTP request.
    """
    return DriveState(request.get('state'))



class BaseDriveHandler(webapp.RequestHandler):
  """Base request handler for drive applications.

  Adds Authorization support for Drive.
  """

  def CreateOAuthFlow(self):
    """Create OAuth2.0 flow controller

    This controller can be used to perform all parts of the OAuth 2.0 dance
    including exchanging an Authorization code.

    Args:
      request: HTTP request to create OAuth2.0 flow for
    Returns:
      OAuth2.0 Flow instance suitable for performing OAuth2.0.
    """
    flow = flow_from_clientsecrets('client_secrets.json', scope='')
    # Dynamically set the redirect_uri based on the request URL. This is extremely
    # convenient for debugging to an alternative host without manually setting the
    # redirect URI.
    flow.redirect_uri = self.request.url.split('?', 1)[0].rsplit('/', 1)[0]
    return flow

  def GetCodeCredentials(self):
    """Create OAuth 2.0 credentials by extracting a code and performing OAuth2.0.

    The authorization code is extracted form the URI parameters. If it is absent,
    None is returned immediately. Otherwise, if it is present, it is used to
    perform step 2 of the OAuth 2.0 web server flow.

    Once a token is received, the user information is fetched from the userinfo
    service and stored in the session. The token is saved in the datastore against
    the user ID received from the userinfo service.

    Args:
      request: HTTP request used for extracting an authorization code and the
               session information.
    Returns:
      OAuth2.0 credentials suitable for authorizing clients or None if
      Authorization could not take place.
    """
    # Other frameworks use different API to get a query parameter.
    code = self.request.get('code')
    if not code:
      # returns None to indicate that no code was passed from Google Drive.
      return None

    # Auth flow is a controller that is loaded with the client information,
    # including client_id, client_secret, redirect_uri etc
    oauth_flow = self.CreateOAuthFlow()

    # Perform the exchange of the code. If there is a failure with exchanging
    # the code, return None.
    try:
      creds = oauth_flow.step2_exchange(code)
    except FlowExchangeError:
      return None

    # Create an API service that can use the userinfo API. Authorize it with our
    # credentials that we gained from the code exchange.
    users_service = CreateService('oauth2', 'v2', creds)

    # Make a call against the userinfo service to retrieve the user's information.
    # In this case we are interested in the user's "id" field.
    userid = users_service.userinfo().get().execute().get('id')

    # Store the user id in the user's cookie-based session.
    session = sessions.LilCookies(self, SESSION_SECRET)
    session.set_secure_cookie(name='userid', value=userid)

    # Store the credentials in the data store using the userid as the key.
    StorageByKeyName(Credentials, userid, 'credentials').put(creds)
    return creds

  def GetSessionCredentials(self):
    """Get OAuth 2.0 credentials for an HTTP session.

    If the user has a user id stored in their cookie session, extract that value
    and use it to load that user's credentials from the data store.

    Args:
      request: HTTP request to use session from.
    Returns:
      OAuth2.0 credentials suitable for authorizing clients.
    """
    # Try to load  the user id from the session
    session = sessions.LilCookies(self, SESSION_SECRET)
    userid = session.get_secure_cookie(name='userid')
    if not userid:
      # return None to indicate that no credentials could be loaded from the
      # session.
      return None

    # Load the credentials from the data store, using the userid as a key.
    creds = StorageByKeyName(Credentials, userid, 'credentials').get()

    # if the credentials are invalid, return None to indicate that the credentials
    # cannot be used.
    if creds and creds.invalid:
      return None

    return creds

  def RedirectAuth(self):
    """Redirect a handler to an authorization page.

    Used when a handler fails to fetch credentials suitable for making Drive API
    requests. The request is redirected to an OAuth 2.0 authorization approval
    page and on approval, are returned to application.

    Args:
      handler: webapp.RequestHandler to redirect.
    """
    flow = self.CreateOAuthFlow()

    # Manually add the required scopes. Since this redirect does not originate
    # from the Google Drive UI, which authomatically sets the scopes that are
    # listed in the API Console.
    flow.scope = ALL_SCOPES

    # Create the redirect URI by performing step 1 of the OAuth 2.0 web server
    # flow.
    uri = flow.step1_get_authorize_url(flow.redirect_uri)

    # Perform the redirect.
    self.redirect(uri)

  def RespondJSON(self, data):
    """Generate a JSON response and return it to the client.

    Args:
      data: The data that will be converted to JSON to return.
    """
    self.response.headers['Content-Type'] = 'application/json'
    self.response.out.write(json.dumps(data))

  def CreateAuthorizedService(self, service, version):
    """Create an authorize service instance.

    The service can only ever retrieve the credentials from the session.

    Args:
      service: Service name (e.g 'drive', 'oauth2').
      version: Service version (e.g 'v1').
    Returns:
      Authorized service or redirect to authorization flow if no credentials.
    """
    # For the service, the session holds the credentials
    creds = self.GetSessionCredentials()
    if creds:
      # If the session contains credentials, use them to create a Drive service
      # instance.
      return CreateService(service, version, creds)
    else:
      # If no credentials could be loaded from the session, redirect the user to
      # the authorization page.
      self.RedirectAuth()

  def CreateDrive(self):
    """Create a drive client instance."""
    return self.CreateAuthorizedService('drive', 'v2')

  def CreateUserInfo(self):
    """Create a user info client instance."""
    return self.CreateAuthorizedService('oauth2', 'v2')

####################################################################################################
  ####  functions created by kernel0722@gmail.com
####################################################################################################
  def createEmptyGDoc(self, service, title, description, parent_id):
    body = {
      'title' : title,
      'description' : description,
      'mimeType' : 'application/vnd.google-apps.document',
      'parents' : [{'id': parent_id}]
      }
    try:
      file = service.files().insert(body=body).execute()
    # print "result : %s" % file
      return file
    except errors.HttpError, error:
      print 'An error occured: %s' % error
      return None

  def createFolder(self, service, folder, description, parent_id):
    body = {
    'title': folder,
    'description': description,
    'mimeType': 'application/vnd.google-apps.folder',
    # 'parents' : [{'id': parent_id}]
    }
    if parent_id:
      body['parents'] = [{'id': parent_id}]
    try:
      file = service.files().insert(body=body).execute()
      return file
    except errors.HttpError, error:
      print 'An error occured: %s' % error
      return None

  def moveFileToFolder(self, service, file_id, folder_id):
    file = service.files().update(fileId=file_id, body={'parents': [{'id':folder_id}]}).execute()

  def renameFile(self, service, file_id, newname):
    file = service.files().update(fileId=file_id, body={'title': newname}).execute()
    return file

  def trashFile(self, service, file_id):
    file = service.files().update(fileId=file_id, body={'labels': {'trashed': True}}).execute()
    

  def createOrUpdateConfigFile(self, service, content, parent_id=None, file_id=None):
    resource = {
      'title' : '.config',
      'description' : 'config file for current directory',
      'mimeType' : 'application/json',
      'parents' : [{'id': parent_id}],
      'labels': {'hidden': True}
      }
    try:
      if file_id == None:
        file = service.files().insert(
          body=resource,
          media_body=MediaInMemoryUpload(content, resource['mimeType'], resumable=True)
          ).execute()
      else:
        file = service.files().update(
          fileId=file_id,
          body={'labels': {'hidden': True}},
          media_body=MediaInMemoryUpload(content, resource['mimeType'], resumable=True)
          ).execute()
      return file
    except errors.HttpError, error:
      print 'An error occured: %s' % error
      return None
  
  #def getFileContent(service, file_id):
  def getFileContent(self, service, downloadUrl):
    resp, content = service._http.request(downloadUrl)
    if resp.status == 200:
      # print 'Status: %s' % resp
      return content
    else:
      print 'An error occurred: %s' % resp
      return None
                     
    
  def getChildrenList(self, service, folder_id, query=None):
    try:
      param = {}
      param['q'] = query
      children = service.children().list(folderId=folder_id, **param).execute()
      return children
    except errors.HttpError, error:
      print 'An error occurred: %s' % error
      return None


  
  # only refresh the specific folder, ignore its' sub-folder (also not change the .config of parent folder)
  # create or update the .config file in the specific folder
  # never set the depth parameter(it's used as a temporary param)
  # valid for node with subnodes or leaf node(a doc file or a folder with a same-named doc file, will do nothing or update the .config)
  
  # return the config of the specific folder

  def updateFolderConfig(self, service, folder):
    folderConfig, subConfig = self.updateSubFolderConfig(service, folder, depth=-1)     
    return subConfig
    
  # def updateFolderConfig(self, service, folder, depth=0):
  #   docId = None
  #   attachments = 0
  #   nodes = {}
  #   configfile = None
  #   subFolder = []
  #   # the format : {docId:[ folderId, title, subNodes, attachments, isExpand]}
  #   # isExpand : False:collapse, True:expand, 2:keep unchanged
  #   # subNodes: True or False
  #   # children = self.getChildrenList(service, folder['id'])
  #   children = self.getChildrenList(service, folder['id'], "trashed = false")
  #   for item in children['items']:
  #     file = service.files().get(fileId=item['id']).execute()
  #     if file['mimeType'] == 'application/vnd.google-apps.document':
  #       if file['title'] == folder['title']:              # the doc with the same name #no need to record
  #         docId = file['id']                           # no need to record this since the .config in parent dir record it
  #       else:
  #         # nodes[file['id']] = [file['id'], file['title'], 0, 0, 0]              #add to the dict
  #         nodes[file['id']] = [file['id'], file['title'], False, False, False]              #add to the dict
  #     elif file['mimeType'] == 'application/vnd.google-apps.folder':       #must be subnode, but not know whether it's leaf node
  #       subFolder.append(file)                  # first record the sub-folders, then traverse them
  #     elif file['title'] == '.config' and file['mimeType'] == 'application/json':
  #       configfile = file
  #     else:                     #the other files are the attachments
  #       attachments += 1

  #   if depth:             # not the first level, just return the summary
  #     if docId == None:
  #       docFile = self.createEmptyGDoc(service, folder['title'], None, folder['id'])
  #       docId = docFile['id']
  #     # return [docId, folder['title'], len(nodes)+len(subFolder), attachments]   # return the [docId, title, subNodes, attachments]
  #     return [docId, len(nodes)+len(subFolder)>=1, attachments>=1]   # return the [docId, subNodes, attachments]
  #   # else, get the detail info
    
  #   for file in subFolder:
  #     ret = self.updateFolderConfig(service, file, depth=1)      
  #     nodes[ret[0]] = [file['id'], file['title'], ret[1], ret[2], 2]  # {docId:[ folderId, title, subNodes, attachments, isExpand]}

  #   subNodes = len(nodes)       # No. of subnode
  #   if configfile:
  #     content = self.getFileContent(service, configfile['downloadUrl'])
  #     nodeConfig = json.loads(content)
  #   else:
  #     nodeConfig = []

  #   flag = 0
  #   i = 0
  #   while i < len(nodeConfig):
  #     if nodeConfig[i][0] not in nodes:               # this node already not exist(deleted), pop it from nodeConfig
  #       nodeConfig.pop(i)
  #       flag = 1
  #       continue
  #     info = nodes.pop(nodeConfig[i][0])              # node exist
  #     if info[4] == 2:                                  # keep the expand state unchanged
  #       info[4] = nodeConfig[i][5]
  #     if info != nodeConfig[i][1:]:                   # node exist, but the info changed
  #       nodeConfig[i][1:] = info
  #       flag = 1
  #     # else the info not changed
  #     i += 1
      
  #   if len(nodes):
  #     flag = 1
  #     for i in nodes:
  #       nodes[i][4] = False                         # all these nodes collapsed
  #       nodeConfig.append([i] + nodes[i])
        
  #   if flag:                                    # the .config need update
  #     if configfile == None:
  #       self.createOrUpdateConfigFile(service, json.dumps(nodeConfig), parent_id=folder['id'])
  #     else:
  #       self.createOrUpdateConfigFile(service, json.dumps(nodeConfig), file_id=configfile['id'])
  #   # else:                                     # the .config already synced, or there no need a .config file(a leaf node)

  #   # # the format : {id:[ docId, title, subNodes, attachments, isExpand]}
  #   # return [docId, folder['title'], subNodes, attachments, 2]      #only valid for recursive call, isExpand == 2 means keep unchanged
  #   # return subNodes
  #   # return (subNodes>=1)                # leaf node or not
  #   return nodeConfig
    


  


  # Notice:
  # only update the specify folder's sub-nodes, the folder's is not updated, because its config reside in the .config of parent folder
  # return the config of the specific folder and  its sub folders (call recursive)
  
  # parameter:
  # folder : the info of a folder, this must be a real folder, never be a file
  # depth == -1 : scan & update only 1 level
  # depth == 1 or not set(default 1): scan & update all its sub folder

  # return:
  # the docId of root directory is 'root', not a real doc id (corresponding doc may not exist)
  # the first list is the info of current foler, nodeConfig is the config of its subnodes
  # [docId, folder['id'], folder['title'], subNodes, attachments, 2], nodeConfig

  # example:
  # # update the spec_folder's all sub-nodes(and sub-sub-...-nodes)
  # folderConfig, subConfig = self.updateSubFolderConfig(service, spec_folder)
  # # update the spec_folder for 1 level(only its sub-nodes)
  # folderConfig, subConfig = self.updateSubFolderConfig(service, spec_folder, depth=-1)     
  
  def updateSubFolderConfig(self, service, folder, depth=1):
    depth += 1
    
    docId = None
    attachments = 0
    nodes = {}
    configfile = None
    subFolder = []
    # the format : {docId:[ folderId, title, subNodes, attachments, isExpand]}
    # isExpand : False:collapse, True:expand, 2:keep unchanged
    # subNodes: True or False
    # children = self.getChildrenList(service, folder['id'])
    children = self.getChildrenList(service, folder['id'], "trashed = false")
    for item in children['items']:
      file = service.files().get(fileId=item['id']).execute()
      if file['mimeType'] == 'application/vnd.google-apps.document':
        if file['title'] == folder['title']:              # the doc with the same name #no need to record
          docId = file['id']                           # no need to record this since the .config in parent dir record it
        else:
          # nodes[file['id']] = [file['id'], file['title'], 0, 0, 0]              #add to the dict
          nodes[file['id']] = [file['id'], file['title'], False, False, False]              #add to the dict
      elif file['mimeType'] == 'application/vnd.google-apps.folder':       #must be subnode, but not know whether it's leaf node
        subFolder.append(file)                  # first record the sub-folders, then traverse them
      elif file['title'] == '.config' and file['mimeType'] == 'application/json':
        configfile = file
      else:                     #the other files are the attachments
        attachments += 1

    if folder['id'] == MyNotes_folderid:        # the root dir, ignore the same-named doc file
      docId = 'root'
    else:
      if docId == None: # if corresponding doc not exist(except root dir), create an empty one
        docFile = self.createEmptyGDoc(service, folder['title'], None, folder['id'])
        docId = docFile['id']

    if depth == 1:
      return [docId, folder['id'], folder['title'], len(nodes)+len(subFolder)>0, attachments>0, 2], []
        
    for file in subFolder:
      # need return [docId, subNodes, attachments]
      # actually, return [docId, folder['id'], folder['title'], subNodes, attachments, 2], nodeConfig 
      subConfig, subsubConfig = self.updateSubFolderConfig(service, file, depth=depth)            # subsubConfig is ignored
      nodes[subConfig[0]] = subConfig[1:]
    
    subNodes = len(nodes)       # No. of subnode
    if configfile:
      content = self.getFileContent(service, configfile['downloadUrl'])
      nodeConfig = json.loads(content)
    else:
      nodeConfig = []

    flag = 0
    i = 0
    while i < len(nodeConfig):
      if nodeConfig[i][0] not in nodes:               # this node already not exist, pop it from nodeConfig
        nodeConfig.pop(i)
        flag = 1
        continue
      info = nodes.pop(nodeConfig[i][0])              # node exist
      if info[4] == 2:                                  # keep the expand state unchanged
        info[4] = nodeConfig[i][5]
      if info != nodeConfig[i][1:]:                   # node exist, but the info changed
        nodeConfig[i][1:] = info
        flag = 1
      # else the info not changed
      i += 1
      
    if len(nodes):
      flag = 1
      for i in nodes:
        nodes[i][4] = False                         # all these nodes collapsed
        nodeConfig.append([i] + nodes[i])
        
    if flag:                                    # the .config need update
      if configfile == None:
        self.createOrUpdateConfigFile(service, json.dumps(nodeConfig), parent_id=folder['id'])
      else:
        self.createOrUpdateConfigFile(service, json.dumps(nodeConfig), file_id=configfile['id'])
    # else:                                     # the .config already synced, or there no need a .config file(a leaf node)

    # # the format : {docId:[ folderId, title, subNodes, attachments, isExpand]}
    # return [docId, folder['title'], subNodes, attachments, 2]      #only valid for recursive call, isExpand == 2 means keep unchanged
    # return nodeConfig
    # return [docId, subNodes, attachments], nodeConfig       # need return [docId, subNodes, attachments]
    return [docId, folder['id'], folder['title'], subNodes>0, attachments>0, 2], nodeConfig   # need return [docId, subNodes, attachments]



  def getConfigFile(self, service, folder_id):
    # children = self.getChildrenList(service, folder_id, "title = '.config'")            # error, why ??
    children = self.getChildrenList(service, folder_id, "title contains '.config' and mimeType = 'application/json' and hidden = true and trashed = false")
    for item in children['items']:
      file = service.files().get(fileId=item['id']).execute()
      if file['title'] == '.config':
        return file
    return None


  # # this func not used
  # # return :
  # # None : .config not exist
  # # not none(a list) : the config data
  # def getConfigFileData(self, service, folder_id):
  #   configfile = self.getConfigFile(service, folder_id)
  #   if configfile:
  #     content = self.getFileContent(service, configfile['downloadUrl'])
  #     return json.loads(content)
  #   else:
  #     return None
  
  def getFolderConfig(self, service, folder_id):
    configfile = self.getConfigFile(service, folder_id)
    if configfile:
      content = self.getFileContent(service, configfile['downloadUrl'])
      return json.loads(content)
    # .config file not exist, scan the folder & create it
    folder = service.files().get(fileId=folder_id).execute()
    # nodeConfig = self.updateFolderConfig(service, folder)
    # return nodeConfig
    folderConfig, subConfig = self.updateSubFolderConfig(service, folder, depth=-1)     
    return subConfig


  def config2ztree(self, config):
    # config = [["15YhJFPsLYOUeK6WAFBokXTdSJMLq09_WL66D-6lc4a0", "15YhJFPsLYOUeK6WAFBokXTdSJMLq09_WL66D-6lc4a0", "mydoc", False, False, False], ["1JmsdpT0SOwpL5DK_1xVOf7p4vlDSt4hXfREF3ZcBv50", "1JmsdpT0SOwpL5DK_1xVOf7p4vlDSt4hXfREF3ZcBv50", "baid", False, False, False], ["1C_sTV40woXO1R67W2m66jqmE8IvtJU9H7S9qpT5QV_0", "1C_sTV40woXO1R67W2m66jqmE8IvtJU9H7S9qpT5QV_0", "this is a folder", False, False, False], ["1z0Cv6vLgTqNwJZCPBESANdIBqsAW79-VgqvJW0M5tIE", "0B7W9AXhTJJq6T0ZvZ0hCRzNJWXc", ".config", True, False, False], ["1uvDVRfOYTBqpsohFvPohhJgJYlruXBYPSGWpXgCg82M", "1uvDVRfOYTBqpsohFvPohhJgJYlruXBYPSGWpXgCg82M", "index", False, False, False], ["1OB-YXFJOszuflpxikR_5U9eZIWCExeEExINiEfW-bgM", "0B7W9AXhTJJq6MEgtX3FzdW1oVG8", "testfolder", True, False, False]]

    # config = []             # this value is also valid

    # the format : [docId, folderId, title, subNodes, attachments, isExpand]
    resp = []
    for i in config:
      #resp.append({'id':i[0], 'name':i[2], 'isParent':i[3]!=0, 'docId':i[1], 'attachments':i[4], 'open':i[5]==1})
      # resp.append({'docId':i[0], 'folderId':i[1], 'name':i[2], 'isParent':i[3]!=0, 'attachments':i[4], 'open':True})  # the open field is not valid in the response data !!!
      # resp.append({'docId':i[0], 'folderId':i[1], 'name':i[2], 'isParent':i[3], 'attachments':i[4]})

      # subNodes is the same with isParent, used for update the node in onAsyncSuccess()
      resp.append({'docId':i[0], 'folderId':i[1], 'name':i[2], 'isParent':i[3], 'attachments':i[4], 'subNodes':i[3]})   
    return resp

  
  def addSubNode_root(self, service, title):
    folder_id = MyNotes_folderid
    # folder_id = getMyNotesFolderId()
    folder = service.files().get(fileId=folder_id).execute()
    # create the new doc 
    newfile = self.createEmptyGDoc(service, title, None, folder_id)
    newnode = [newfile['id'], newfile['id'], newfile['title'], False, False, False]
    # update the .config file
    configfile = self.getConfigFile(service, folder_id)
    if configfile:
      content = self.getFileContent(service, configfile['downloadUrl'])
      nodeConfig = json.loads(content)
      nodeConfig.append(newnode)
      self.createOrUpdateConfigFile(service, json.dumps(nodeConfig), file_id=configfile['id'])
    else:               # .config not exist
      nodeConfig = self.updateFolderConfig(service, folder)
    # the format : [docId, folderId, title, subNodes, attachments, isExpand]  
    rootNodeConfig = ["root", folder_id, folder['title'], True, False, True]
    return [rootNodeConfig] + nodeConfig
  
    
  # return the whole node
  # def addSubNode(self, service, folder_id, title):
  def addSubNode(self, service, docId, title):
    # first check the node is a folder or a doc
    docfile = service.files().get(fileId=docId).execute()
    doc_parent_id = docfile['parents'][0]['id']
    doc_parent_folder = service.files().get(fileId=doc_parent_id).execute()
    if docfile['title'] == doc_parent_folder['title']:              # this node corresponding a folder
      folder = doc_parent_folder
      folder_id = doc_parent_id
    else:                                                       # this node corresponding a doc file
      folder = self.createFolder(service, docfile['title'], None, doc_parent_id)    #create a same-named folder
      folder_id = folder['id']
      # move the doc file to this folder
      self.moveFileToFolder(service, file_id=docfile['id'], folder_id=folder_id)
    # now create the new doc in the folder which id is folder_id
    newfile = self.createEmptyGDoc(service, title, None, folder_id)
    newnode = [newfile['id'], newfile['id'], newfile['title'], False, False, False]
    # add the new doc id to the .config file
    configfile = self.getConfigFile(service, folder_id)
    if configfile:
      content = self.getFileContent(service, configfile['downloadUrl'])
      nodeConfig = json.loads(content)
      nodeConfig.append(newnode)
      self.createOrUpdateConfigFile(service, json.dumps(nodeConfig), file_id=configfile['id'])
    else:               # .config not exist
      # nodeConfig = []
      # nodeConfig.append(newnode)
      # self.createOrUpdateConfigFile(service, json.dumps(nodeConfig), parent_id=folder['id'])
      nodeConfig = self.updateFolderConfig(service, folder)             # contain the newnode

    # set the the parent of the newly created node
    # docParentNodeConfig = [docId, folderId, folder['title'], True, None, True]
    docParentNodeConfig = [docId, folder_id, folder['title'], True, None, True]
    # update the .config of parent folder (update the foldId(for leaf node which is a doc) and subNodes, isExpand field)
    parent_id = folder['parents'][0]['id']
    parent_configfile = self.getConfigFile(service, parent_id)
    if parent_configfile:
      parent_nodeConfig = json.loads(self.getFileContent(service, parent_configfile['downloadUrl']))
      for i in parent_nodeConfig:
        if i[0] == docId:               # [docId, folderId, title, subNodes, attachments, isExpand]
          i[1] = folder_id
          i[3] = True                   # it has subnodes
          i[5] = True                   # expand the node
          docParentNodeConfig[4] = i[4]
      self.createOrUpdateConfigFile(service, json.dumps(parent_nodeConfig), file_id=parent_configfile['id'])
    # else:      # .config of parent folder not exist, this case should not appear, and it'll create the .config automatically

    return [docParentNodeConfig] + nodeConfig
    # return nodeConfig         # return the whole node
    # return [newnode]            # or the incremental subnode

  # # this func(use folder_id as the unique parameter) may cause circular create folder(if the select node(not its children) not update or multi users operation)
  # def addSubNode(self, service, folder_id, title):
  #   file = service.files().get(fileId=folder_id).execute()
  #   parent_id = file['parents'][0]['id']
  #   # check file['parents']['isRoot'] ?
  #   docId = None
  #   if file['mimeType'] == 'application/vnd.google-apps.document':              # a doc without attachments (leaf node )
  #     # it's a doc, create a same-named folder
  #     docId = folder_id
  #     folder = self.createFolder(service, file['title'], None, parent_id)
  #     folder_id = folder['id']
  #     # move the doc file to this folder
  #     self.moveFileToFolder(service, file_id=file['id'], folder_id=folder_id)
  #   else:
  #     folder = file
  #   # now create the new doc in the folder which id is folder_id
  #   newfile = self.createEmptyGDoc(service, title, None, folder_id)
  #   newnode = [newfile['id'], newfile['id'], newfile['title'], False, False, False]
  #   # add the new doc id to the .config file
  #   configfile = self.getConfigFile(service, folder_id)
  #   if configfile:
  #     content = self.getFileContent(service, configfile['downloadUrl'])
  #     nodeConfig = json.loads(content)
  #     nodeConfig.append(newnode)
  #     self.createOrUpdateConfigFile(service, json.dumps(nodeConfig), file_id=configfile['id'])
  #   else:
  #     nodeConfig = []
  #     nodeConfig.append(newnode)
  #     self.createOrUpdateConfigFile(service, json.dumps(nodeConfig), parent_id=folder['id'])
      
  #   # update the .config of parent folder (update the foldId(for leaf node which is a doc) and subNodes, isExpand field)
  #   parent_configfile = self.getConfigFile(service, parent_id)
  #   if parent_configfile:
  #     parent_nodeConfig = json.loads(self.getFileContent(service, parent_configfile['downloadUrl']))
  #     for i in parent_nodeConfig:
  #       if i[0] == docId:               # [docId, folderId, title, subNodes, attachments, isExpand]
  #         i[1] = folder_id
  #         i[3] = True                   # it has subnodes
  #         i[5] = True                   # expand the node
  #     self.createOrUpdateConfigFile(service, json.dumps(parent_nodeConfig), file_id=parent_configfile['id'])
  #   # else:      # .config of parent folder not exist, this case should not appear, and it'll create the .config automatically
      
  #   return nodeConfig         # return the whole node
  #   # return [newnode]            # or the incremental subnode

  # def renameNode(self, service, docId, newname):
  #   # first check the node is a folder or a doc
  #   # the format : [docId, folderId, title, subNodes, attachments, isExpand]
  #   docfile = service.files().get(fileId=docId).execute()
  #   doc_parent_id = docfile['parents'][0]['id']
  #   doc_parent_folder = service.files().get(fileId=doc_parent_id).execute()
  #   if docfile['title'] == doc_parent_folder['title']:              # this node corresponding a folder
  #     node_folder = doc_parent_folder
  #     node_folder_id = node_folder['id']
  #     node_parent_folder_id = doc_parent_folder['parents'][0]['id']
  #     if node_folder['labels']['trashed'] == True:
  #       return [deletedNode]
  #     self.renameFile(service, docId, newname)
  #     self.renameFile(service, node_folder_id, newname)
  #     # update the folder & return the sub nodes list
  #     selNodeConfig = [docId, node_folder_id, newname, None, None, None]    #none means not change
  #     # get its parent folder config
  #     configfile = self.getConfigFile(service, node_parent_folder_id)
  #     if configfile:
  #       content = self.getFileContent(service, configfile['downloadUrl'])
  #       parentNodeConfig = json.loads(content)
  #       for i in parentNodeConfig:
  #         if i[0] == docId:
  #           if i[1:3] != selNodeConfig[1:3]:                      # this may not occur except you change the file manually
  #             i[1:3] = selNodeConfig[1:3]
  #             self.createOrUpdateConfigFile(service, json.dumps(parentNodeConfig), file_id=configfile['id'])
  #           return [i]
  #       return [deletedNode]
  #     else:
  #       # .config file not exist, scan the folder & create it
  #       node_parent_folder = service.files().get(fileId=node_parent_folder_id).execute()
  #       parentNodeConfig = self.updateFolderConfig(service, node_parent_folder)
  #       for i in parentNodeConfig:
  #         if i[0] == docId:
  #           return [i]
  #       # docfile not found, this node is deleted
  #       return [deletedNode]
  #   else:
  #     if docfile['labels']['trashed'] == True:
  #       return [deletedNode]
  #     node_parent_folder = doc_parent_folder
  #     node_parent_folder_id = doc_parent_id
  #     self.renameFile(service, docId, newname)                  # rename
  #     selNodeConfig = [docId, docId, newname, False, False, False]
  #     # get its parent folder config
  #     configfile = self.getConfigFile(service, node_parent_folder_id)
  #     if configfile:
  #       content = self.getFileContent(service, configfile['downloadUrl'])
  #       parentNodeConfig = json.loads(content)
  #       for i in parentNodeConfig:
  #         if i[0] == docId:
  #           if i != selNodeConfig:                      # this may not occur except you change the file manually
  #             i = selNodeConfig
  #             self.createOrUpdateConfigFile(service, json.dumps(parentNodeConfig), file_id=configfile['id'])
  #           return [i]
  #       return [deletedNode]
  #     else:
  #       # .config file not exist, scan the folder & create it
  #       parentNodeConfig = self.updateFolderConfig(service, node_parent_folder)
  #       for i in parentNodeConfig:
  #         if i[0] == docId:
  #           return [i]
  #       # docfile not found, this node is deleted
  #       return [deletedNode]

  def renameNode_root(self, service, newname):
    folder_id = MyNotes_folderid
    # folder_id = getMyNotesFolderId()
    folder = self.renameFile(service, folder_id, newname)
    rootNodeConfig = ["root", folder_id, folder['title'], True, False, True]
    return [rootNodeConfig]
    

  def renameNode(self, service, docId, newname):
    # first check the node is a folder or a doc
    # the format : [docId, folderId, title, subNodes, attachments, isExpand]
    docfile = service.files().get(fileId=docId).execute()
    doc_parent_id = docfile['parents'][0]['id']
    doc_parent_folder = service.files().get(fileId=doc_parent_id).execute()
    if docfile['title'] == doc_parent_folder['title']:              # this node corresponding a folder
      node_folder = doc_parent_folder
      node_folder_id = node_folder['id']
      node_parent_folder_id = doc_parent_folder['parents'][0]['id']
      if node_folder['labels']['trashed'] == True:
        return [deletedNode]
      self.renameFile(service, docId, newname)
      self.renameFile(service, node_folder_id, newname)
      # update the folder & return the sub nodes list
      # selNodeConfig = [docId, node_folder_id, newname, None, None, None]    #none means not change
      selNodeConfig = [docId, node_folder_id, newname, False, False, False]    #set false for default
    else:
      if docfile['labels']['trashed'] == True:
        return [deletedNode]
      node_parent_folder = doc_parent_folder
      node_parent_folder_id = doc_parent_id
      self.renameFile(service, docId, newname)                  # rename
      selNodeConfig = [docId, docId, newname, False, False, False]
    # get its parent folder config
    configfile = self.getConfigFile(service, node_parent_folder_id)
    if configfile:
      content = self.getFileContent(service, configfile['downloadUrl'])
      parentNodeConfig = json.loads(content)
      for i in parentNodeConfig:
        if i[0] == docId:
          if i[1:3] != selNodeConfig[1:3]:                      # this may not occur except you change the file manually
            i[1:3] = selNodeConfig[1:3]
            self.createOrUpdateConfigFile(service, json.dumps(parentNodeConfig), file_id=configfile['id'])
          return [i]
      return [deletedNode]              # not find the corresponding item
    else:             # .config file not exist, this should neven occur except you change the file manually
                        # and the parent will be auto scanned & updated
      return selNodeConfig
      # # .config file not exist, scan the folder & create it
      # node_parent_folder = service.files().get(fileId=node_parent_folder_id).execute()
      # parentNodeConfig = self.updateFolderConfig(service, node_parent_folder)
      # for i in parentNodeConfig:
      #   if i[0] == docId:
      #     return [i]
      #   # docfile not found, this node is deleted
      #   return [deletedNode]


  def removeNode(self, service, docId):
    # first check the node is a folder or a doc
    docfile = service.files().get(fileId=docId).execute()
    doc_parent_id = docfile['parents'][0]['id']
    doc_parent_folder = service.files().get(fileId=doc_parent_id).execute()
    if docfile['title'] == doc_parent_folder['title']:              # this node corresponding to a folder
      node_folder = doc_parent_folder
      node_folder_id = node_folder['id']
      node_parent_folder_id = doc_parent_folder['parents'][0]['id']
      self.trashFile(service, node_folder_id)
    else:                       # a doc
      node_parent_folder = doc_parent_folder
      node_parent_folder_id = doc_parent_id
      self.trashFile(service, docId)
    # remove the item from .config file
    configfile = self.getConfigFile(service, node_parent_folder_id)
    if configfile:
      content = self.getFileContent(service, configfile['downloadUrl'])
      parentNodeConfig = json.loads(content)
      for i in parentNodeConfig:
        if i[0] == docId:
          # delete this item
          parentNodeConfig.remove(i)
          self.createOrUpdateConfigFile(service, json.dumps(parentNodeConfig), file_id=configfile['id'])
          break
      return [deletedNode]
    else:
      # # .config file not exist, scan the folder & create it   -- will scan automatically 
      # node_parent_folder = service.files().get(fileId=node_parent_folder_id).execute()
      # parentNodeConfig = self.updateFolderConfig(service, node_parent_folder)
      return [deletedNode]
        

  # depth:
  # depth == -1 : scan & update only 1 level
  # depth == 1 or not set(default 1): scan & update all its sub folder

  def refreshSubNodes(self, service, docId, depth=1):
    # first check the node is a folder or a doc
    # the format : [docId, folderId, title, subNodes, attachments, isExpand]
    if docId == 'root':
      node_folder = service.files().get(fileId=MyNotes_folderid).execute()
      selNodeConfig, subNodesConfig = self.updateSubFolderConfig(service, node_folder, depth)
      selNodeConfig[5] = True
      return selNodeConfig, subNodesConfig
    
    # else selected node is not root
    docfile = service.files().get(fileId=docId).execute()
    doc_parent_id = docfile['parents'][0]['id']
    doc_parent_folder = service.files().get(fileId=doc_parent_id).execute()
    if docfile['title'] == doc_parent_folder['title']:              # this node corresponding to a folder
      node_folder = doc_parent_folder
      node_folder_id = node_folder['id']
      node_parent_folder_id = doc_parent_folder['parents'][0]['id']
      if node_folder['labels']['trashed'] == True:
        return deletedNode, []
      # update the folder & return the sub nodes list(scan for 1 level)
      selNodeConfig, subNodesConfig = self.updateSubFolderConfig(service, node_folder, depth)
      selNodeConfig[5] = False
    else:
      if docfile['labels']['trashed'] == True:
        return deletedNode, []
      node_parent_folder = doc_parent_folder
      node_parent_folder_id = doc_parent_id
      selNodeConfig = [docfile['id'], docfile['id'], docfile['title'], False, False, False]
      subNodesConfig = []

    configfile = self.getConfigFile(service, node_parent_folder_id)
    if configfile:
      content = self.getFileContent(service, configfile['downloadUrl'])
      parentConfig = json.loads(content)
      for i in parentConfig:
        if i[0] == selNodeConfig[0]:         # the docId matched
          selNodeConfig[5] = i[5]
          if i[1:5] != selNodeConfig[1:5]:
            i[1:5] = selNodeConfig[1:5]
            self.createOrUpdateConfigFile(service, json.dumps(parentConfig), file_id=configfile['id'])
          return i, subNodesConfig
      # not found the corresponding item
      # print "not found the corresponding item"
      parentConfig.append(selNodeConfig)
      self.createOrUpdateConfigFile(service, json.dumps(parentConfig), file_id=configfile['id'])
      # return selNodeConfig, subNodesConfig
    # else:      # .config of parent folder not exist, this case should not appear, and it'll create the .config automatically
    return selNodeConfig, subNodesConfig

  

####################################################################################################

    

class MyDatabase(db.Model):
  author = db.UserProperty()
  content = db.StringProperty(multiline=True)
  date = db.DateTimeProperty(auto_now_add=True)
    

####################################################################################################

class InitNoteHandler(BaseDriveHandler):
  def get(self):
    """Called when HTTP GET requests are received by the web application."""
    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return
    try:
      folder_id = getMyNotesFolderId()
      if folder_id:
        folder = service.files().get(fileId=folder_id).execute()
        greeting = 'current notes folder is [<b>%s</b>], its fileid is [<b>%s</b>]' % (folder['title'], folder_id)
      else:
        # greeting = "<span style='color:red'>notes folder not set</span>"
        greeting = ""

      template_values = {
        # 'url': url,
        'greeting': greeting
        }

      self.response.headers['Content-Type'] = 'text/html'
      self.response.headers['Content-Encoding'] = 'utf-8'
      path = os.path.join(os.path.dirname(__file__), 'initnote.html')
      self.response.out.write(template.render(path, template_values))
      
    except AccessTokenRefreshError:
      self.RedirectAuth()

  def post(self):
    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return
    try:
      notename = self.request.get('notename')
      folder_id = self.request.get('folderid')
      # self.response.headers['Content-Type'] = 'text/html'
      # self.response.headers['Content-Encoding'] = 'utf-8'
      # self.response.out.write("name : (%s)" % notename);
      if notename:
        folder = self.createFolder(service, notename, "This is My Notes", None)   # create a notes folder in the root directory
        folder_id = folder['id']
      elif not folder_id:
        self.response.headers['Content-Type'] = 'text/html'
        self.response.out.write("no valid name or foldid !")
        return
      # now get the valid folder_id, save it
      entity = MyDatabase.get_or_insert("MyNotes_folderid")
      entity.content = folder_id
      entity.put()
      self.redirect('/')
    except AccessTokenRefreshError:
      self.RedirectAuth()




  
class MainPage(BaseDriveHandler):
  """Web handler for the main page.

  Handles requests and returns the user interface for Open With and Create
  cases. Responsible for parsing the state provided from the Drive UI and acting
  appropriately.
  """

  def get(self):
    """Handle GET for Create New and Open With.

    This creates an authorized client, and checks whether a resource id has
    been passed or not. If a resource ID has been passed, this is the Open
    With use-case, otherwise it is the Create New use-case.
    """
    
    # Generate a state instance for the request, this includes the action, and
    # the file id(s) that have been sent from the Drive user interface.
    # drive_state = DriveState.FromRequest(self.request)
    # if drive_state.action == 'open' and len(drive_state.ids) > 0:
    #   code = self.request.get('code')
    #   if code:
    #     code = '?code=%s' % code
    #   self.redirect('/#edit/%s%s' % (drive_state.ids[0], code))
    #   return

    # Fetch the credentials by extracting an OAuth 2.0 authorization code from
    # the request URL. If the code is not present, redirect to the OAuth 2.0
    # authorization URL.
    
    creds = self.GetCodeCredentials()
    if not creds:
      return self.RedirectAuth()

    # Extract the numerical portion of the client_id from the stored value in
    # the OAuth flow. You could also store this value as a separate variable
    # somewhere.
    client_id = self.CreateOAuthFlow().client_id.split('.')[0].split('-')[0]


    viewMode = self.request.get('view')
    
    # get the notes folder id
    if getMyNotesFolderId():            # note folder already created
      # if viewMode == 't':               # use jquery layout
      #   self.RenderTemplate()             # the index page
      # else:
      #   self.RenderFrameset()           # default
      if viewMode == 'f':
        self.RenderFrameset()
      else:
        self.RenderTemplate()
      return
    else:
      self.redirect('/initnote')
      return

    # self.redirect('/tree')
    # self.RenderTemplate()

  def RenderTemplate(self):
    """Render a named template in a context."""
    self.response.headers['Content-Type'] = 'text/html'
    self.response.headers['Content-Encoding'] = 'utf-8'    
    # self.response.out.write(INDEX_HTML)
    self.response.out.write(open(SibPath('index.jquerylayout.html')).read())

    # self.response.out.write("<b>hello</b>")
    # self.response.out.write("<iframe src='https://docs.google.com/document/d/1hbJ8GkA2Nd_o2ZZvTsbnNes_yY9yJWrTBiKlDNytYog/edit'></iframe>")

  def RenderFrameset(self):
    self.response.headers['Content-Type'] = 'text/html'
    self.response.headers['Content-Encoding'] = 'utf-8'    
    self.response.out.write('''
<html>
<frameset cols="15%,85%">
  <frame name="view" id="treeview" src="/tree" />
  <frame name="editor" id="editframe" src="about:blank" />
</frameset>
</html>''')


    
class ServiceHandler(BaseDriveHandler):
  """Web handler for the service to read and write to Drive."""

  def post(self):
    """Called when HTTP POST requests are received by the web application.

    The POST body is JSON which is deserialized and used as values to create a
    new file in Drive. The authorization access token for this action is
    retreived from the data store.
    """
    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return

    # Load the data that has been posted as JSON
    data = self.RequestJSON()

    # Create a new file data structure.
    resource = {
      'title': data['title'],
      'description': data['description'],
      'mimeType': data['mimeType'],
    }
    try:
      # Make an insert request to create a new file. A MediaInMemoryUpload
      # instance is used to upload the file body.
      resource = service.files().insert(
          body=resource,
          media_body=MediaInMemoryUpload(
              data.get('content', ''),
              data['mimeType'],
              resumable=True)
      ).execute()
      # Respond with the new file id as JSON.
      self.RespondJSON(resource['id'])
    except AccessTokenRefreshError:
      # In cases where the access token has expired and cannot be refreshed
      # (e.g. manual token revoking) redirect the user to the authorization page
      # to authorize.
      self.RedirectAuth()

  def get(self):
    """Called when HTTP GET requests are received by the web application.

    Use the query parameter file_id to fetch the required file's metadata then
    content and return it as a JSON object.

    Since DrEdit deals with text files, it is safe to dump the content directly
    into JSON, but this is not the case with binary files, where something like
    Base64 encoding is more appropriate.
    """
    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return
    try:
      # Requests are expected to pass the file_id query parameter.
      file_id = self.request.get('file_id')
      if file_id:
        # Fetch the file metadata by making the service.files().get method of
        # the Drive API.
        f = service.files().get(fileId=file_id).execute()
        downloadUrl = f.get('downloadUrl')
        # If a download URL is provided in the file metadata, use it to make an
        # authorized request to fetch the file ontent. Set this content in the
        # data to return as the 'content' field. If there is no downloadUrl,
        # just set empty content.
        if downloadUrl:
          resp, f['content'] = service._http.request(downloadUrl)
        else:
          f['content'] = ''
      else:
        f = None
      # Generate a JSON response with the file data and return to the client.
      self.RespondJSON(f)
    except AccessTokenRefreshError:
      # Catch AccessTokenRefreshError which occurs when the API client library
      # fails to refresh a token. This occurs, for example, when a refresh token
      # is revoked. When this happens the user is redirected to the
      # Authorization URL.
      self.RedirectAuth()

  def put(self):
    """Called when HTTP PUT requests are received by the web application.

    The PUT body is JSON which is deserialized and used as values to update
    a file in Drive. The authorization access token for this action is
    retreived from the data store.
    """
    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return
    # Load the data that has been posted as JSON
    data = self.RequestJSON()
    try:
      # Create a new file data structure.
      content = data.get('content')
      if 'content' in data:
        data.pop('content')
      if content is not None:
        # Make an update request to update the file. A MediaInMemoryUpload
        # instance is used to upload the file body. Because of a limitation, this
        # request must be made in two parts, the first to update the metadata, and
        # the second to update the body.
        resource = service.files().update(
            fileId=data['resource_id'],
            newRevision=self.request.get('newRevision', False),
            body=data,
            media_body=MediaInMemoryUpload(
                content, data['mimeType'], resumable=True)
            ).execute()
      else:
        # Only update the metadata, a patch request is prefered but not yet
        # supported on Google App Engine; see
        # http://code.google.com/p/googleappengine/issues/detail?id=6316.
        resource = service.files().update(
            fileId=data['resource_id'],
            newRevision=self.request.get('newRevision', False),
            body=data).execute()
      # Respond with the new file id as JSON.
      self.RespondJSON(resource['id'])
    except AccessTokenRefreshError:
      # In cases where the access token has expired and cannot be refreshed
      # (e.g. manual token revoking) redirect the user to the authorization page
      # to authorize.
      self.RedirectAuth()

  def RequestJSON(self):
    """Load the request body as JSON.

    Returns:
      Request body loaded as JSON or None if there is no request body.
    """
    if self.request.body:
      return json.loads(self.request.body)


class UserHandler(BaseDriveHandler):
  """Web handler for the service to read user information."""

  def get(self):
    """Called when HTTP GET requests are received by the web application."""
    # Create a Drive service
    service = self.CreateUserInfo()
    if service is None:
      return
    try:
          # for debug(must run before authenticate)
      print '''<html>
<frameset cols="15%,85%">
  <frame src="/" name="view"/>
  <frame src="about:blank" name="editor" id="editframe"/>
</frameset>
</html>'''
      return

  
      result = service.userinfo().get().execute()
      # Generate a JSON response with the file data and return to the client.
      self.RespondJSON(result)
    except AccessTokenRefreshError:
      # Catch AccessTokenRefreshError which occurs when the API client library
      # fails to refresh a token. This occurs, for example, when a refresh token
      # is revoked. When this happens the user is redirected to the
      # Authorization URL.
      self.RedirectAuth()


class AboutHandler(BaseDriveHandler):
  """Web handler for the service to read user information."""

  def get(self):
    """Called when HTTP GET requests are received by the web application."""
    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return
    self.response.headers['Content-Type'] = 'text/html'
    self.response.headers['Content-Encoding'] = 'utf-8'
    self.response.out.write(open(SibPath('about.html')).read())
    return


####################################################################################################  
class jQueryLayoutHanlder(BaseDriveHandler):
  def get(self):
    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return
    try:
      self.response.headers['Content-Type'] = 'text/html'
      self.response.headers['Content-Encoding'] = 'utf-8'    
      self.response.out.write(open(SibPath('index.jquerylayout.html')).read())
    except AccessTokenRefreshError:
      self.RedirectAuth()
    
class FrameLayoutHandler(BaseDriveHandler):
  def get(self):
    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return
    try:
      self.response.headers['Content-Type'] = 'text/html'
      self.response.headers['Content-Encoding'] = 'utf-8'    
      self.response.out.write('''
<html>
<frameset cols="15%,85%">
  <frame name="view" id="treeview" src="/tree" />
  <frame name="editor" id="editframe" src="about:blank" />
</frameset>
</html>''')
    except AccessTokenRefreshError:
      self.RedirectAuth()
####################################################################################################        

class TestHandler(BaseDriveHandler):
  def get(self):
    """Called when HTTP GET requests are received by the web application."""

    self.response.headers['Content-Type'] = 'text/html'
    self.response.headers['Content-Encoding'] = 'utf-8'
    self.response.out.write(open(SibPath('test.html')).read())
    # self.response.out.write(open(SibPath('index.html')).read())
    return

    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return

    global MyNotes_folderid
    MyNotes_folderid = getMyNotesFolderId()
    
    id = self.request.get('id')
    
    self.response.headers['Content-Type'] = 'text/html'
    self.response.out.write("========================================  test  ====================================================<br>")
    self.response.out.write("<hr>")
    print "<hr>"

    # folder = service.files().get(fileId=MyNotes_folderid).execute()       # the root folder, setting in /initnote page
    # print service.files().get(fileId=fileid).execute()
    # print "####################################################################################################"
    #print self.getFileContent(service, file['downloadUrl'])
    return
  
class TestJsonHandler(BaseDriveHandler):
  def get(self):
    """Called when HTTP GET requests are received by the web application."""
    # Create a Drive service
    service = self.CreateUserInfo()
    if service is None:
      return
    try:
      resource = {
      'name' : "Tom",
      'content' :"believe or not believe is a problem"
      }
      self.response.headers['Content-Type'] = 'text/plain'
      # self.response.out.write(json.dumps(resource))
      print "OK"
      print json.dumps(resource)
      return

      result = service.userinfo().get().execute()
      # Generate a JSON response with the file data and return to the client.
      self.RespondJSON(result)
    except AccessTokenRefreshError:
      # Catch AccessTokenRefreshError which occurs when the API client library
      # fails to refresh a token. This occurs, for example, when a refresh token
      # is revoked. When this happens the user is redirected to the
      # Authorization URL.
      self.RedirectAuth()

  def post(self):
    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return

    global MyNotes_folderid
    # # for debug
    # config = [["15YhJFPsLYOUeK6WAFBokXTdSJMLq09_WL66D-6lc4a0", "15YhJFPsLYOUeK6WAFBokXTdSJMLq09_WL66D-6lc4a0", "mynotes", False, False, False], ["1JmsdpT0SOwpL5DK_1xVOf7p4vlDSt4hXfREF3ZcBv50", "1JmsdpT0SOwpL5DK_1xVOf7p4vlDSt4hXfREF3ZcBv50", "baid", False, False, False], ["1C_sTV40woXO1R67W2m66jqmE8IvtJU9H7S9qpT5QV_0", "1C_sTV40woXO1R67W2m66jqmE8IvtJU9H7S9qpT5QV_0", "this is a folder", False, False, False], ["1z0Cv6vLgTqNwJZCPBESANdIBqsAW79-VgqvJW0M5tIE", "0B7W9AXhTJJq6T0ZvZ0hCRzNJWXc", ".config", True, False, False], ["1uvDVRfOYTBqpsohFvPohhJgJYlruXBYPSGWpXgCg82M", "1uvDVRfOYTBqpsohFvPohhJgJYlruXBYPSGWpXgCg82M", "index", False, False, False], ["1OB-YXFJOszuflpxikR_5U9eZIWCExeEExINiEfW-bgM", "0B7W9AXhTJJq6MEgtX3FzdW1oVG8", "testfolder", True, False, False]]

    # config = [["15YhJFPsLYOUeK6WAFBokXTdSJMLq09_WL66D-6lc4a0", "15YhJFPsLYOUeK6WAFBokXTdSJMLq09_WL66D-6lc4a0", "debug1", False, False, False], ["1JmsdpT0SOwpL5DK_1xVOf7p4vlDSt4hXfREF3ZcBv50", "1JmsdpT0SOwpL5DK_1xVOf7p4vlDSt4hXfREF3ZcBv50", "debug2", False, False, False]]
    # config = [["15YhJFPsLYOUeK6WAFBokXTdSJMLq09_WL66D-6lc4a0", "15YhJFPsLYOUeK6WAFBokXTdSJMLq09_WL66D-6lc4a0", "debug3", False, False, False]]

    # #resp = []
    # # resp.append({'docId':i[0], 'folderId':i[1], 'name':i[2], 'isParent':i[3]!=0, 'attachments':i[4]})
    # #return resp

    docId = self.request.get('docId')                 # the id of a node(doc)
    folder_id = self.request.get('folderId')             # the id of a node(folder) (not always reliable) !!!!!
    name = self.request.get('name')             # the name of the node
    level = self.request.get('level')           # The root node's level = 0, and next level = 1, ...
    action = self.request.get('action')         # the action, such as 'addSubNode'
    param = self.request.get('param')            
    otherParam = self.request.get('otherParam') # other parameters

    # if folder_id=='':                                  # get the root nodes
    #   #folder_id = MyNotes_folderid
    #   folder_id = getMyNotesFolderId()                  # get the root folder id
    
    MyNotes_folderid = getMyNotesFolderId()

    if docId == None:
      docId = 'root'
      
    # [docId, folder['title'], subNodes, attachments, isExpand]

  
    # for debug
    # self.response.headers['Content-Type'] = 'text/plain'
    # self.response.out.write("ok")
    # print "debug ......"
    # print [docId, folder_id, name, level, action, param, otherParam]
    # return
####################################################################################################      
    if action == 'addSubNode':
      # config = self.addSubNode(service, folder_id, param)
      if docId == 'root':
        config = self.addSubNode_root(service, param)
      else:
        config = self.addSubNode(service, docId, param)
####################################################################################################              
    elif action == 'renameNode':
      if docId == 'root':
        config = self.renameNode_root(service, param)
      else:
        config = self.renameNode(service, docId, param)
####################################################################################################              
    elif action == 'removeNode':
      config = self.removeNode(service, docId)
####################################################################################################            
    elif action == 'inittree':
      rootFolder = service.files().get(fileId=MyNotes_folderid).execute()       # the root folder, setting in /initnote page
      config = self.getFolderConfig(service, MyNotes_folderid)
      # config = []
      # rootNode = [docId, folder['id'], folder['title'], len(config)>0, attachments , True]
      rootNode = ["root", rootFolder['id'], rootFolder['title'], len(config)>0, False , True]
      config = [rootNode] + config
####################################################################################################
    elif action == 'refreshCurNode':
      folderConfig, subConfig = self.refreshSubNodes(service, docId, depth=-1)  # only update for 1 level
      config = [folderConfig] + subConfig 
####################################################################################################            
    elif action == 'refreshAllSubNodes':
      folderConfig, subConfig = self.refreshSubNodes(service, docId, depth=1)
      config = [folderConfig] + subConfig 
####################################################################################################           
    else:
      if not folder_id:
        folder_id = MyNotes_folderid
      config = [dummyNode] + self.getFolderConfig(service, folder_id)
####################################################################################################

    resp = self.config2ztree(config)
    self.RespondJSON(resp)
    # self.RespondJSON([])
    return
    
    # resource = [{ id:'01', name:'n1', isParent:true}]           #will cause 500 Internal Server Error
    # resource = [{ 'id':'01', 'name':'n1', 'isParent':'true'},
    #               { 'id':'02',	'name':'n2',	'isParent':'false'},
    #               { 'id':'03',	'name':'n3',	'isParent':'true'},
    #               { 'id':'04',	'name':'n4',	'isParent':'false'}];
  

class TreeViewHandler(BaseDriveHandler):
  def get(self):
    """Called when HTTP GET requests are received by the web application."""
    # Create a Drive service
    service = self.CreateDrive()
    if service is None:
      return
    try:
      self.response.headers['Content-Type'] = 'text/html'
      self.response.headers['Content-Encoding'] = 'utf-8'
      self.response.out.write(open(SibPath('index.html')).read())
      return
    except AccessTokenRefreshError:
      self.RedirectAuth()


class MediaInMemoryUpload(MediaUpload):
  """MediaUpload for a chunk of bytes.

  Construct a MediaFileUpload and pass as the media_body parameter of the
  method. For example, if we had a service that allowed plain text:
  """

  def __init__(self, body, mimetype='application/octet-stream',
               chunksize=256*1024, resumable=False):
    """Create a new MediaBytesUpload.

    Args:
      body: string, Bytes of body content.
      mimetype: string, Mime-type of the file or default of
        'application/octet-stream'.
      chunksize: int, File will be uploaded in chunks of this many bytes. Only
        used if resumable=True.
      resumable: bool, True if this is a resumable upload. False means upload
        in a single request.
    """
    self._body = body
    self._mimetype = mimetype
    self._resumable = resumable
    self._chunksize = chunksize

  def chunksize(self):
    """Chunk size for resumable uploads.

    Returns:
      Chunk size in bytes.
    """
    return self._chunksize

  def mimetype(self):
    """Mime type of the body.

    Returns:
      Mime type.
    """
    return self._mimetype

  def size(self):
    """Size of upload.

    Returns:
      Size of the body.
    """
    return len(self._body)

  def resumable(self):
    """Whether this upload is resumable.

    Returns:
      True if resumable upload or False.
    """
    return self._resumable

  def getbytes(self, begin, length):
    """Get bytes from the media.

    Args:
      begin: int, offset from beginning of file.
      length: int, number of bytes to read, starting at begin.

    Returns:
      A string of bytes read. May be shorter than length if EOF was reached
      first.
    """
    return self._body[begin:begin + length]



# Create an WSGI application suitable for running on App Engine
application = webapp.WSGIApplication(
    [('/', MainPage), ('/svc', ServiceHandler), ('/about', AboutHandler),
     ('/user', UserHandler),
     ('/test', TestHandler),
     # ('/testajax', TestAjaxHandler),
     # ('/testpost', TestPostHandler),
     ('/testjson', TestJsonHandler),
     ('/tree', TreeViewHandler),
     ('/initnote', InitNoteHandler),
     ('/t', jQueryLayoutHanlder),
     ('/f', FrameLayoutHandler),
     ],
    # XXX Set to False in production.
    # debug=True
    debug=False
)


def main():
  """Main entry point for executing a request with this handler."""
  run_wsgi_app(application)


if __name__ == "__main__":
    main()

